home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / pluginy Firefox / 11587 / 11587.xpi / chrome / aviary.jar / content / pearlutil-grab.js < prev    next >
Text File  |  2009-08-11  |  32KB  |  937 lines

  1. /* Copyright (c) 2006-2009 Pearl Crescent, LLC.  All Rights Reserved. */
  2. /* vim: set sw=2 sts=2 ts=8 et syntax=javascript: */
  3.  
  4. /*
  5.  * This file is part of the Pearl Crescent Utility Functions Library.
  6.  */
  7.  
  8. if (!com) var com = {};
  9. if (!com.aviary) com.aviary = {};
  10. if (!com.aviary.talon) com.aviary.talon = {};
  11. if (!com.aviary.talon.grab) com.aviary.talon.grab =
  12. {
  13.   kSelectionPopupElemID: "AviarySelectionPopup",
  14.   kPortionCanvasID: "AviaryPortionCanvas",   // Gray w/crosshairs.
  15.   kPortionWinCanvasID: "AviaryWindowCanvas", // Window image.
  16.   kPortionBoxID: "AviaryPortionBox",
  17.   kPortionInfoID: "AviaryPortionInfoBox",
  18.   kPortionDimensionBoxID: "AviaryPortionDimensionBox", // in info box
  19.   kPortionBtnBoxID: "AviaryPortionBtnBox",             // in info box
  20.   kDefaultSelectionFillStyle: "rgba(128,128,128,0.5)",
  21.   kMinWidth: 6, // When adjusting, box will be 6x6 or larger.
  22.   kLeftAlignInfoBox: true, // Change as desired.
  23.  
  24.   mUtilObj: null,
  25.   mWindow: null,
  26.   mCanvasRect: {},
  27.   mSelectionFillStyle: null,
  28.   mMaxCanvasDimension: 0,
  29.   mAllowAdjustment: false,
  30.   mCaptureFunc: null,
  31.   mCaptureArg: null,
  32.  
  33.   mBrowserElem: null,
  34.   mIsDrawing: false,
  35.   mIsAdjusting: false, // Initial rect has been drawn and may be adjusted.
  36.   mIsMovingBox: false,
  37.   mIsResizingBox: false,
  38.   mDidCapture: false,
  39.   mPopupScreenX: 0, // mPopupScreenX, mPopupScreenY are popup's top, left.
  40.   mPopupScreenY: 0,
  41.   mStartX: 0,
  42.   mStartY: 0,
  43.   mSelectionWidth: 0,  // Only valid when mIsAdjusting is true.
  44.   mSelectionHeight: 0, // Only valid when mIsAdjusting is true.
  45.   mAdjustOffsetX: 0,   // Offset within the box where mousedown occurred.
  46.   mAdjustOffsetY: 0,
  47.   mAdjustHorz: 0,
  48.   mAdjustVert: 0,
  49.   mPopupElem: null,      // Popup (the container for everything).
  50.   mCanvasElem: null,     // Gray w/crosshairs.
  51.   mWinCanvasElem: null,  // Image of window contents.
  52.   mPortionBoxElem: null, // Surrounded by dashed lines; shown during adjustment.
  53.   mInfoBoxElem: null,    // Container for dimension labels and buttons.
  54.   mButtonBoxElem: null,  // Container for buttons (within info box).
  55.   mDimensionBoxLabelElem1: null,  // label element with pearl-template="PROP"
  56.   mDimensionBoxLabelElem2: null,  // label element with pearl-template="PROP"
  57.   mDimensionBoxLabelTemplate1: null,  // e.g., "%S w" or "%1$S x %2$S pixels"
  58.   mDimensionBoxLabelTemplate2: null,  // e.g., "%S h" or null
  59.   mFFZoomLevel: 1,
  60.  
  61.   // aCaptureFunc is called like:
  62.   //     aCaptureFunc(browserWin, portionRect, captureArg);
  63.   //   where portionRect includes: startX, startY, pageW, pageH properties.
  64.   // If the user cancelled the grab, aCaptureFunc() will be called with a
  65.   //   null portionRect.
  66.   init: function(aBrowserWin, aBrowserWinVisRect, aSelectionFillStyle,
  67.                  aMaxCanvasDimension, aAllowAdjustment, aCaptureFunc,
  68.                  aCaptureArg)
  69.   {
  70.     this.mUtilObj = com.aviary.talon.pearlutil;
  71.  
  72.     this.mWindow = aBrowserWin;
  73.     this.mCanvasRect = aBrowserWinVisRect;
  74.     this.mSelectionFillStyle = (aSelectionFillStyle)
  75.                                   ? aSelectionFillStyle
  76.                                   : this.kDefaultSelectionFillStyle;
  77.  
  78.     if (aMaxCanvasDimension && aMaxCanvasDimension > 0)
  79.       this.mMaxCanvasDimension = aMaxCanvasDimension;
  80.     else
  81.     {
  82.       var ua = navigator.userAgent;
  83.       var isWinOrMac = (ua.indexOf("Macintosh") >= 0
  84.                         || ua.indexOf("Windows") >= 0);
  85.       this.mMaxCanvasDimension = isWinOrMac ? 32767 : 32766;
  86.     }
  87.  
  88.     this.mAllowAdjustment = aAllowAdjustment;
  89.     this.mCaptureFunc = aCaptureFunc;
  90.     this.mCaptureArg = aCaptureArg;
  91.  
  92.     this.mBrowserElem = top.getBrowser().selectedBrowser;
  93.     this.mIsDrawing = false;
  94.     this.mIsAdjusting = false;
  95.     this.mIsMovingBox = false;
  96.     this.mIsResizingBox = false;
  97.     this.mDidCapture = false;
  98.     this.mPopupScreenX = 0;
  99.     this.mPopupScreenY = 0;
  100.     this.mStartX = 0;
  101.     this.mStartY = 0;
  102.     this.mSelectionWidth = 0;
  103.     this.mSelectionHeight = 0;
  104.     this.mAdjustOffsetX = 0;
  105.     this.mAdjustOffsetY = 0;
  106.     this.mAdjustHorz = 0;
  107.     this.mAdjustVert = 0;
  108.     this.mPortionBoxElem = null;
  109.     this.mInfoBoxElem = null;
  110.     this.mButtonBoxElem = null;
  111.     this.mDimensionBoxLabelElem1 = null;
  112.     this.mDimensionBoxLabelElem2 = null;
  113.     this.mDimensionBoxLabelTemplate1 = null;
  114.     this.mDimensionBoxLabelTemplate2 = null;
  115.     this.mFFZoomLevel = 1;
  116.     this.mWinCanvasElem = document.getElementById(this.kPortionWinCanvasID);
  117.     this.mPopupElem = document.getElementById(this.kSelectionPopupElemID);
  118.     if (this.mPopupElem)
  119.     {
  120.       document.popupNode = this.mBrowserElem;
  121.  
  122.       this.mPortionBoxElem = document.getElementById(this.kPortionBoxID);
  123.       if (this.mPortionBoxElem)
  124.         this.mPortionBoxElem.setAttribute("hidden", "true");
  125.       var dimBoxElem = document.getElementById(this.kPortionDimensionBoxID);
  126.       if (dimBoxElem && this.mUtilObj)
  127.       {
  128.         var dimElems = this.mUtilObj.GetElementsByAttr(dimBoxElem,
  129.                                                        "pearl-template", null);
  130.         if (dimElems)
  131.         {
  132.           this.mDimensionBoxLabelElem1 = dimElems[0];
  133.           this.mDimensionBoxLabelTemplate1 =
  134.                                     dimElems[0].getAttribute("pearl-template");
  135.           if (dimElems.length > 1)
  136.           {
  137.             this.mDimensionBoxLabelElem2 = dimElems[1];
  138.             this.mDimensionBoxLabelTemplate2 =
  139.                                     dimElems[1].getAttribute("pearl-template");
  140.           }
  141.         }
  142.       }
  143.       this.mButtonBoxElem = document.getElementById(this.kPortionBtnBoxID);
  144.       this.mInfoBoxElem = document.getElementById(this.kPortionInfoID);
  145.       if (this.mInfoBoxElem)
  146.       {
  147.         // Set top and left so size is available, and hide info box for now.
  148.         this.mInfoBoxElem.style.top = "0px";
  149.         this.mInfoBoxElem.style.left = "0px";
  150.       }
  151.       this.showElement(this.mInfoBoxElem, false);
  152.       this.showElement(this.mButtonBoxElem, false);
  153.  
  154.       this.mFFZoomLevel = (this.mBrowserElem.boxObject.width
  155.                           / this.mBrowserElem.contentWindow.innerWidth);
  156.  
  157.       var r = { startX: 0, startY: 0, pageW: 0, pageH: 0 };
  158.       this.getContentWindowSize(r);
  159.       this.capPortionRect(r);
  160.       this.mCanvasElem = document.getElementById(this.kPortionCanvasID);
  161.       this.setCanvasSize(this.mCanvasElem, r.pageW, r.pageH);
  162.       var self = this;
  163.       window.setTimeout(function() { self.delayedInit(); }, 0);
  164.     }
  165.   },
  166.  
  167.   // delayedInit() is needed so popups such as menus have a chance to close.
  168.   delayedInit: function()
  169.   {
  170.     if (this.mPopupElem)
  171.     {
  172.       // Remove old max width and height so as not to interfere with natural
  173.       // width and height (which are used in initCanvasInPopup() to set
  174.       // max width and max height).
  175.       this.mPopupElem.style.removeProperty("max-width");
  176.       this.mPopupElem.style.removeProperty("max-height");
  177.  
  178.       this.mPopupElem.showPopup(this.mBrowserElem, -1, -1, "popup", "topleft",
  179.                                 "overlap");
  180.       this.mPopupElem.addEventListener("mousedown", this, true);
  181.       this.mPopupElem.addEventListener("mousemove", this, true);
  182.     }
  183.   },
  184.  
  185.   // Called via "onpopupshown"
  186.   initCanvasInPopup: function()
  187.   {
  188.     this.mDidCapture = false;
  189.  
  190.     if (this.mPopupElem)
  191.     {
  192.       this.mPopupScreenX = this.mPopupElem.boxObject.screenX;
  193.       this.mPopupScreenY = this.mPopupElem.boxObject.screenY;
  194.     }
  195.  
  196.     try
  197.     {
  198.       // Convert from zoomed units to XUL pixel units.
  199.       this.mCanvasRect.pageW =
  200.                         Math.round(this.mCanvasRect.pageW * this.mFFZoomLevel);
  201.       this.mCanvasRect.pageH =
  202.                         Math.round(this.mCanvasRect.pageH * this.mFFZoomLevel);
  203.       this.capPortionRect(this.mCanvasRect);
  204.       var canvascontext = this.mCanvasElem.getContext("2d");
  205.       canvascontext.fillStyle = this.mSelectionFillStyle;
  206.       canvascontext.fillRect(0, 0,
  207.                              this.mCanvasRect.pageW, this.mCanvasRect.pageH);
  208.  
  209.       this.drawWindowInCanvas();
  210.     }
  211.     catch (e) {}
  212.  
  213.     if (this.mPopupElem)
  214.     {
  215.       // Ensure that popup will not grow larger than window content size.
  216.       this.mPopupElem.style.maxWidth = this.mPopupElem.boxObject.width + "px";
  217.       this.mPopupElem.style.maxHeight = this.mPopupElem.boxObject.height + "px";
  218.     }
  219.   },
  220.  
  221.   capPortionRect: function(aRect)
  222.   {
  223.     if (aRect)
  224.     {
  225.       if (aRect.pageW > this.mMaxCanvasDimension)
  226.         aRect.pageW = this.mMaxCanvasDimension;
  227.       if (aRect.pageH > this.mMaxCanvasDimension)
  228.         aRect.pageH = this.mMaxCanvasDimension;
  229.     }
  230.   },
  231.  
  232.   // Sets pageW and pageH properties inside aSizeObj (units are XUL pixels).
  233.   getContentWindowSize: function(aSizeObj)
  234.   {
  235.     aSizeObj.pageW = 0;
  236.     aSizeObj.pageH = 0;
  237.  
  238.     if (this.mBrowserElem)
  239.     {
  240.       var theWin = this.mBrowserElem.contentWindow;
  241.       aSizeObj.pageW = this.mBrowserElem.boxObject.width;
  242.       aSizeObj.pageH = this.mBrowserElem.boxObject.height;
  243.       var hasHorzSB = (theWin.scrollMaxX > 0);
  244.       var hasVertSB = (theWin.scrollMaxY > 0);
  245.       if (!hasHorzSB && !hasVertSB)
  246.         return; // No scrollbars so no further adjustments needed.
  247.  
  248.       try
  249.       {
  250.         // Add hidden element with height=100% and use to account for SB height.
  251.         var tmpElem = theWin.document.createElement("div");
  252.         tmpElem.setAttribute("style", "visibility: hidden; z-index: -1;"
  253.                              + " position: fixed; top: 0px; left: 0px;"
  254.                              + " margin: 0px; padding: 0px; border: none;"
  255.                              + " width: 100%; height: 100%");
  256.         theWin.document.body.appendChild(tmpElem);
  257.         if (hasVertSB)
  258.           aSizeObj.pageW = Math.round(tmpElem.offsetWidth * this.mFFZoomLevel);
  259.         if (hasHorzSB)
  260.           aSizeObj.pageH = Math.round(tmpElem.offsetHeight * this.mFFZoomLevel);
  261.         theWin.document.body.removeChild(tmpElem);
  262.       } catch (e) {}
  263.     }
  264.   },
  265.  
  266.   drawWindowInCanvas: function()
  267.   {
  268.     if (!this.mWinCanvasElem)
  269.       return;
  270.  
  271.     var bgColor = "rgb(255,255,255)";
  272.     if (this.mUtilObj)
  273.     {
  274.       if (!this.mUtilObj.GetBoolPref("browser.display.use_system_colors",
  275.                                      false))
  276.       {
  277.         bgColor = this.mUtilObj.GetLocalizedStrPref(
  278.                                   "browser.display.background_color", bgColor);
  279.       }
  280.     }
  281.     // else get system background color... but there is no JS API.
  282.  
  283.     this.setCanvasSize(this.mWinCanvasElem,
  284.                        this.mCanvasRect.pageW, this.mCanvasRect.pageH);
  285.  
  286.     var context = this.mWinCanvasElem.getContext("2d");
  287.  
  288.     // Draw the page image.
  289.     context.clearRect(0, 0, this.mCanvasRect.pageW, this.mCanvasRect.pageH);
  290.     context.save();
  291.     context.scale(this.mFFZoomLevel, this.mFFZoomLevel);
  292.  
  293.     var right = Math.round(this.mCanvasRect.pageW / this.mFFZoomLevel);
  294.     var bottom = Math.round(this.mCanvasRect.pageH / this.mFFZoomLevel);
  295.     context.drawWindow(this.mWindow, this.mCanvasRect.startX,
  296.                        this.mCanvasRect.startY, right, bottom, bgColor);
  297.  
  298.     context.restore();
  299.   },
  300.  
  301.   setCanvasSize: function(aCanvas, aWidth, aHeight)
  302.   {
  303.     aCanvas.style.width = aWidth + "px";
  304.     aCanvas.style.height = aHeight + "px";
  305.     aCanvas.style.maxWidth = aWidth + "px";
  306.     aCanvas.style.maxHeight = aHeight + "px";
  307.     aCanvas.width = aWidth;
  308.     aCanvas.height = aHeight;
  309.   },
  310.  
  311.   handleEvent: function(aEvent)
  312.   {
  313.     if (aEvent.type == "keypress")
  314.     {
  315.       aEvent.preventDefault();
  316.       aEvent.stopPropagation();
  317.       this.cleanUp(); // Cancelled.
  318.       return true;
  319.     }
  320.  
  321.     // The remaining events are mouse events.
  322.     var clientX = aEvent.screenX - this.mPopupScreenX;
  323.     var clientY = aEvent.screenY - this.mPopupScreenY;
  324.  
  325.     if (aEvent.type == "mousemove")
  326.     {
  327.       var canvascontext = this.mCanvasElem.getContext("2d");
  328.       if (!canvascontext) return false;
  329.  
  330.       if (!this.mIsDrawing)
  331.         return this.drawCrossHairs(canvascontext, clientX, clientY);
  332.  
  333.       if (aEvent.ctrlKey || aEvent.shiftKey || aEvent.altKey || aEvent.metaKey)
  334.         return false;
  335.  
  336.       var doUpdateCaptureRegion = false;
  337.       var captureRect = null; // Only used in !this.mIsAdjusting case.
  338.       var right = 0;
  339.       var bottom = 0;
  340.       if (!this.mIsAdjusting)
  341.       {
  342.         right = clientX;
  343.         bottom = clientY;
  344.         captureRect = this.getCaptureRect(clientX, clientY);
  345.         doUpdateCaptureRegion = true;
  346.       }
  347.       else if (this.mIsMovingBox)
  348.       {
  349.         // Move the box, ensuring all sides stay within the canvas.
  350.         this.mStartX = (clientX - this.mAdjustOffsetX);
  351.         if (this.mStartX < 0)
  352.           this.mStartX = 0;
  353.         else if (this.mStartX + this.mSelectionWidth > this.mCanvasRect.pageW)
  354.           this.mStartX = (this.mCanvasRect.pageW - this.mSelectionWidth);
  355.  
  356.         this.mStartY = (clientY - this.mAdjustOffsetY);
  357.         if (this.mStartY < 0)
  358.           this.mStartY = 0;
  359.         else if (this.mStartY + this.mSelectionHeight > this.mCanvasRect.pageH)
  360.           this.mStartY = (this.mCanvasRect.pageH - this.mSelectionHeight);
  361.  
  362.         this.positionBox(this.mStartX, this.mStartY, null, null);
  363.         right = this.mStartX + this.mSelectionWidth;
  364.         bottom = this.mStartY + this.mSelectionHeight;
  365.         doUpdateCaptureRegion = true;
  366.       }
  367.       else if (this.mIsResizingBox)
  368.       {
  369.         // Adjust mouse coords to account for orig. click position in grabber.
  370.         clientX += this.mAdjustOffsetX;
  371.         clientY += this.mAdjustOffsetY;
  372.  
  373.         right = this.mStartX + this.mSelectionWidth;
  374.         bottom = this.mStartY + this.mSelectionHeight;
  375.  
  376.         if (1 == this.mAdjustHorz)
  377.         {
  378.           this.mStartX = clientX;
  379.           if (this.mStartX < 0)
  380.             this.mStartX = 0;
  381.           if (this.mStartX > (right - this.kMinWidth))
  382.             this.mStartX = right - this.kMinWidth;
  383.         }
  384.         else if (-1 == this.mAdjustHorz)
  385.         {
  386.           right = clientX;
  387.           if (right < (this.mStartX + this.kMinWidth))
  388.             right = this.mStartX + this.kMinWidth;
  389.           else if (right > this.mCanvasRect.pageW)
  390.             right = this.mCanvasRect.pageW;
  391.         }
  392.  
  393.         if (1 == this.mAdjustVert)
  394.         {
  395.           this.mStartY = clientY;
  396.           if (this.mStartY < 0)
  397.             this.mStartY = 0;
  398.           if (this.mStartY > (bottom - this.kMinWidth))
  399.             this.mStartY = bottom - this.kMinWidth;
  400.         }
  401.         else if (-1 == this.mAdjustVert)
  402.         {
  403.           bottom = clientY;
  404.           if (bottom < (this.mStartY + this.kMinWidth))
  405.             bottom = this.mStartY + this.kMinWidth;
  406.           else if (bottom > this.mCanvasRect.pageH)
  407.             bottom = this.mCanvasRect.pageH;
  408.         }
  409.         this.mSelectionWidth = right - this.mStartX;
  410.         this.mSelectionHeight = bottom - this.mStartY;
  411.         this.positionBox(this.mStartX, this.mStartY,
  412.                          this.mSelectionWidth, this.mSelectionHeight);
  413.         doUpdateCaptureRegion = true;
  414.       }
  415.  
  416.       if (doUpdateCaptureRegion)
  417.       {
  418.         this.drawGrayRects(canvascontext, captureRect, right, bottom);
  419.         this.updateInfoBox(captureRect); // Also shows box if hidden.
  420.       }
  421.  
  422.       aEvent.preventDefault();
  423.       aEvent.stopPropagation();
  424.       return true;
  425.     }
  426.     else if (aEvent.type == "mousedown")
  427.     {
  428.       if (aEvent.ctrlKey || aEvent.shiftKey || aEvent.altKey || aEvent.metaKey)
  429.         return false;
  430.       if (aEvent.button != 0)
  431.         return false;
  432.  
  433.       if (this.mIsAdjusting)
  434.       {
  435.         if (aEvent.target.id == this.kPortionBoxID) // In the box?
  436.         {
  437.           this.mAdjustOffsetX = clientX - this.mStartX;
  438.           this.mAdjustOffsetY = clientY - this.mStartY;
  439.           this.mIsMovingBox = true;
  440.           this.showElement(this.mButtonBoxElem, false);
  441.           this.updateInfoBox(null);
  442.         }
  443.         else // In a grabber.
  444.         {
  445.           var e = aEvent.target;
  446.           this.mAdjustHorz = this.getIntAttribute(e, "adjustHorz");
  447.           this.mAdjustVert = this.getIntAttribute(e, "adjustVert");
  448.  
  449.           if (1 == this.mAdjustHorz)
  450.             this.mAdjustOffsetX = (this.mStartX - clientX);
  451.           else
  452.           {
  453.             var right = this.mStartX + this.mSelectionWidth;
  454.             this.mAdjustOffsetX = this.mAdjustHorz * (clientX - right);
  455.           }
  456.  
  457.           if (1 == this.mAdjustVert)
  458.             this.mAdjustOffsetY = (this.mStartY - clientY);
  459.           else
  460.           {
  461.             var bottom = this.mStartY + this.mSelectionHeight;
  462.             this.mAdjustOffsetY = this.mAdjustVert * (clientY - bottom);
  463.           }
  464.  
  465.           // To avoid mouse cursor "blink", temporarily set the cursor on
  466.           // the box and canvas background to match this grabber's cursor.
  467.           var cursor = e.style.cursor;
  468.           this.setElementCursor(this.mPopupElem, cursor);
  469.           this.setElementCursor(this.mPortionBoxElem, cursor);
  470.  
  471.           this.mIsResizingBox = true;
  472.           this.showElement(this.mButtonBoxElem, false);
  473.           this.updateInfoBox(null);
  474.         }
  475.  
  476.         return true;
  477.       }
  478.       else if (this.mIsDrawing) // workaround for lost mouseup event
  479.         return true;
  480.  
  481.       // First mousedown.
  482.       this.mIsDrawing = true;
  483.       this.mStartX = clientX;
  484.       this.mStartY = clientY;
  485.  
  486.       if (this.mPopupElem)
  487.       {
  488.         this.mPopupElem.addEventListener("mouseup", this, true);
  489.         this.mPopupElem.addEventListener("keypress", this, true);
  490.       }
  491.  
  492.       aEvent.preventDefault();
  493.       aEvent.stopPropagation();
  494.       return true;
  495.     }
  496.     else if (aEvent.type == "mouseup")
  497.     {
  498.       aEvent.preventDefault();
  499.       aEvent.stopPropagation();
  500.  
  501.       if (this.mIsAdjusting)
  502.       {
  503.         if (this.mIsMovingBox)
  504.         {
  505.           this.mIsMovingBox = false;
  506.           this.showElement(this.mButtonBoxElem, true);
  507.         }
  508.         else if (this.mIsResizingBox)
  509.         {
  510.           this.setElementCursor(this.mPopupElem, "default");
  511.           this.setElementCursor(this.mPortionBoxElem, "move");
  512.           this.mIsResizingBox = false;
  513.           this.showElement(this.mButtonBoxElem, true);
  514.         }
  515.         this.updateInfoBox(null);
  516.       }
  517.       else if (this.mIsDrawing)
  518.       {
  519.         var isValidRect = (this.mStartX != clientX && this.mStartY != clientY);
  520.         if (isValidRect)
  521.         {
  522.           var r = this.getCaptureRect(clientX, clientY);
  523.  
  524.           // Ensure that rect coords are in top-bottom and left-right order.
  525.           this.mStartX = r.startX;
  526.           this.mStartY = r.startY;
  527.           this.mSelectionWidth = r.pageW;
  528.           this.mSelectionHeight = r.pageH;
  529.  
  530.           if (!this.mAllowAdjustment)
  531.             this.completeGrab();  // Do not use aEvent after this point.
  532.           else
  533.             this.enterAdjustmentPhase();
  534.         }
  535.       }
  536.  
  537.       return true;
  538.     }
  539.     else if (aEvent.type == "click")
  540.     {
  541.       if (this.mIsAdjusting && 2 == aEvent.detail)
  542.         this.completeGrab(); // Do not use aEvent after this point.
  543.  
  544.       return true;
  545.     }
  546.   },
  547.  
  548.   // Returns true if crosshairs are drawn.
  549.   drawCrossHairs: function(aCanvasCtxt, aClientX, aClientY)
  550.   {
  551.     // Provide initial crosshairs feedback before mouse is pressed.
  552.     aCanvasCtxt.clearRect(0, 0, this.mCanvasRect.pageW, this.mCanvasRect.pageH);
  553.     aCanvasCtxt.fillStyle = this.mSelectionFillStyle;
  554.     aCanvasCtxt.fillRect(0, 0, this.mCanvasRect.pageW, this.mCanvasRect.pageH);
  555.  
  556.     if (aClientX < 0 || aClientX > this.mCanvasRect.pageW ||
  557.         aClientY < 0 || aClientY > this.mCanvasRect.pageH)
  558.     {
  559.        return false;
  560.     }
  561.  
  562.     aCanvasCtxt.fillStyle = "rgba(255,255,255,0.5)";
  563.     aCanvasCtxt.fillRect(aClientX, 0, 1, this.mCanvasRect.pageH);
  564.     aCanvasCtxt.fillRect(0, aClientY, this.mCanvasRect.pageW, 1);
  565.     return true;
  566.   },
  567.  
  568.   // One corner of the area that is not drawn gray is (aClientX, aClientY).
  569.   // The diagonally opposite corner is (this.mStartX, this.mStartY).
  570.   // It is OK to pass null for aCaptureRect.
  571.   drawGrayRects: function(aCanvasCtxt, aCaptureRect, aClientX, aClientY)
  572.   {
  573.     // Highlight current selection rectangle by clearing entire layer and
  574.     // then redrawing 4 gray rects on sides.
  575.     aCanvasCtxt.clearRect(0, 0, this.mCanvasRect.pageW, this.mCanvasRect.pageH);
  576.     aCanvasCtxt.fillStyle = this.mSelectionFillStyle;
  577.  
  578.     var r = aCaptureRect ? aCaptureRect
  579.                          : this.getCaptureRect(aClientX, aClientY);
  580.     if (0 != r.startX)
  581.       aCanvasCtxt.fillRect(0, 0, r.startX, this.mCanvasRect.pageH);
  582.     var w = this.mCanvasRect.pageW - r.startX;
  583.     if (0 != r.startY && 0 != w)
  584.       aCanvasCtxt.fillRect(r.startX, 0, w, r.startY);
  585.     w -= r.pageW;
  586.     var h = this.mCanvasRect.pageH - r.startY;
  587.     if (0 != w && 0 != h)
  588.       aCanvasCtxt.fillRect(r.startX + r.pageW, r.startY, w, h);
  589.     h -= r.pageH;
  590.     if (0 != r.pageW && 0 != h)
  591.       aCanvasCtxt.fillRect(r.startX, r.startY + r.pageH, r.pageW, h);
  592.   },
  593.  
  594.   enterAdjustmentPhase: function()
  595.   {
  596.     // Put up grab handles and "save" button (and dbl-click handler).
  597.     this.mIsAdjusting = true;
  598.     if (this.mPortionBoxElem)
  599.     {
  600.       var box = this.mPortionBoxElem;
  601.       box.style.position = "absolute";
  602.       box.style.overflow = "hidden";
  603.       this.positionBox(this.mStartX, this.mStartY,
  604.                        this.mSelectionWidth, this.mSelectionHeight);
  605.       box.style.MozOutlineWidth = "1px";
  606.       box.style.MozOutlineStyle = "dashed";
  607.       box.style.MozOutlineColor = "black";
  608.       this.setElementCursor(box, "move");
  609.       if (this.mUtilObj)
  610.       {
  611.         const kStrName = "REGION_CAPTURE_TOOLTIP";
  612.         var tt = this.mUtilObj.GetLocalizedString(kStrName);
  613.         if (tt && tt != kStrName)
  614.           box.setAttribute("tooltiptext", tt);
  615.       }
  616.       box.addEventListener("click", this, true);
  617.       box.addEventListener("mousedown", this, true);
  618.       box.removeAttribute("hidden");
  619.       this.showElement(this.mButtonBoxElem, true);
  620.       this.updateInfoBox(null);
  621.     }
  622.  
  623.     if (this.mPopupElem)
  624.     {
  625.       // Remove mousedown handler and crosshairs cursor from background.
  626.       this.mPopupElem.removeEventListener("mousedown", this, true);
  627.       this.setElementCursor(this.mPopupElem, "default");
  628.     }
  629.   },
  630.  
  631.   getIntAttribute: function(aElem, aAttr)
  632.   {
  633.     if (!aElem || !aAttr)
  634.       return 0;
  635.  
  636.     return parseInt(aElem.getAttribute(aAttr), 10);
  637.   },
  638.  
  639.   setElementCursor: function(aElem, aCursorName)
  640.   {
  641.     if (aElem && aCursorName)
  642.       aElem.style.setProperty("cursor", aCursorName, "important");
  643.   },
  644.  
  645.   positionBox: function(aLeft, aTop, aWidth, aHeight)
  646.   {
  647.     if (this.mPortionBoxElem)
  648.     {
  649.       const kBorderSize = 1; // Must match box.style.MozOutlineWidth set above.
  650.       aLeft += kBorderSize;
  651.       aTop += kBorderSize;
  652.       this.mPortionBoxElem.style.left = "" + aLeft + "px";
  653.       this.mPortionBoxElem.style.top = "" + aTop + "px";
  654.       if (aWidth)
  655.       {
  656.         var innerW = aWidth - (3 * kBorderSize);  // Why 3 * and not 2 *???
  657.         this.mPortionBoxElem.style.width = "" + innerW + "px";
  658.       }
  659.       if (aHeight)
  660.       {
  661.         var innerH = aHeight - (2 * kBorderSize);
  662.         this.mPortionBoxElem.style.height = "" + innerH + "px";
  663.       }
  664.     }
  665.   },
  666.  
  667.   // Reposition, update width and height labels, and show info box.
  668.   // If aCaptureRect is null, coordinates are taken from member variables.
  669.   updateInfoBox: function(aCaptureRect)
  670.   {
  671.     if (!this.mInfoBoxElem)
  672.       return;
  673.  
  674.     var left;
  675.     var top;
  676.     var width;
  677.     var height;
  678.  
  679.     if (aCaptureRect)
  680.     {
  681.       left = aCaptureRect.startX;
  682.       top = aCaptureRect.startY;
  683.       width = aCaptureRect.pageW;
  684.       height = aCaptureRect.pageH;
  685.     }
  686.     else
  687.     {
  688.       left = this.mStartX;
  689.       top = this.mStartY;
  690.       width = this.mSelectionWidth;
  691.       height = this.mSelectionHeight;
  692.     }
  693.  
  694.     // Update dimensions text (one or two labels).
  695.     if (this.mDimensionBoxLabelElem1 && this.mUtilObj)
  696.     {
  697.       if (this.mDimensionBoxLabelElem2)
  698.       {
  699.         this.mDimensionBoxLabelElem1.value = this.mUtilObj
  700.                 .GetFormattedLocalizedString(this.mDimensionBoxLabelTemplate1,
  701.                                              [width], 1);
  702.         this.mDimensionBoxLabelElem2.value = this.mUtilObj
  703.                 .GetFormattedLocalizedString(this.mDimensionBoxLabelTemplate2,
  704.                                              [height], 1);
  705.       }
  706.       else
  707.       {
  708.         this.mDimensionBoxLabelElem1.value = this.mUtilObj
  709.                 .GetFormattedLocalizedString(this.mDimensionBoxLabelTemplate1,
  710.                                              [width, height], 2);
  711.       }
  712.     }
  713.  
  714.     // Set width of info box to match width of region.  The box may become
  715.     //   wider (as needed to accommodate its contents).
  716.     this.mInfoBoxElem.style.width = "" + width + "px";
  717.     if (0 == this.mInfoBoxElem.boxObject.width)
  718.     {
  719.        // Need to show it to obtain valid width.
  720.       this.showElement(this.mInfoBoxElem, true);
  721.     }
  722.     var infoWidth = this.mInfoBoxElem.boxObject.width;
  723.     var infoHeight = this.mInfoBoxElem.boxObject.height;
  724.  
  725.     // To avoid jumpiness, hide info box while we adjust it.
  726.     this.showElement(this.mInfoBoxElem, false);
  727.     var keepHidden = false;
  728.  
  729.     // Keep the info elements on the popup (don't let popup stretch).
  730.     // Place info below the box, aligned on the right edge if it will fit.
  731.     // If not, try above or inside box (moving it to the right if necessary).
  732.     var newTop = top + height;
  733.     if (infoHeight + newTop >= this.mCanvasRect.pageH)
  734.     {
  735.       // Not enough space below the box.
  736.       if (top > infoHeight)  // Place it above the box.
  737.         newTop = top - infoHeight;
  738.       else if (infoHeight < height)  // Place it inside.
  739.         newTop = top + height - infoHeight - 10;
  740.       else
  741.         keepHidden = true; // No room for info box.
  742.     }
  743.  
  744.     var newLeft;
  745.     if (this.kLeftAlignInfoBox)
  746.     {
  747.       newLeft = left;
  748.       if (newLeft + infoWidth > this.mCanvasRect.pageW)
  749.         newLeft = this.mCanvasRect.pageW - infoWidth;
  750.     }
  751.     else
  752.       newLeft = left + width - infoWidth;
  753.  
  754.     if (newLeft < 0)
  755.       newLeft = 0;
  756.     if (infoWidth > this.mCanvasRect.pageW)
  757.       keepHidden = true; // No room for info box.
  758.  
  759.     this.mInfoBoxElem.style.top = "" + newTop + "px";
  760.     this.mInfoBoxElem.style.left = "" + newLeft + "px";
  761.  
  762.     if (!keepHidden)
  763.       keepHidden = (0 == width || 0 == height); // Hide if nothing to capture.
  764.     if (!keepHidden)
  765.       this.showElement(this.mInfoBoxElem, true);
  766.   },
  767.  
  768.   showElement: function(aElem, aDoShow)
  769.   {
  770.     if (aElem)
  771.     {
  772.       if (aDoShow)
  773.         aElem.removeAttribute("hidden");
  774.       else
  775.         aElem.setAttribute("hidden", "true");
  776.     }
  777.   },
  778.  
  779.   getCaptureRect: function(aEventClientX, aEventClientY)
  780.   {
  781.     var l, t, r, b;
  782.     if (this.mStartX < aEventClientX)
  783.     {
  784.       l = this.mStartX;
  785.       r = aEventClientX;
  786.     }
  787.     else
  788.     {
  789.       l = aEventClientX;
  790.       r = this.mStartX;
  791.     }
  792.     if (l < 0) l = 0;
  793.     if (r > this.mCanvasRect.pageW) r = this.mCanvasRect.pageW;
  794.  
  795.     if (this.mStartY < aEventClientY)
  796.     {
  797.       t = this.mStartY;
  798.       b = aEventClientY;
  799.     }
  800.     else
  801.     {
  802.       t = aEventClientY;
  803.       b = this.mStartY;
  804.     }
  805.     if (t < 0) t = 0;
  806.     if (b > this.mCanvasRect.pageH) b = this.mCanvasRect.pageH;
  807.  
  808.     return { startX: l, startY: t, pageW: r - l, pageH: b - t };
  809.   },
  810.  
  811.   completeGrab: function(aGrabParam)
  812.   {
  813.     this.mDidCapture = true;
  814.     this.cleanUp();
  815.  
  816.     if (this.mCaptureFunc)
  817.     {
  818.       // TODO: sometimes the captured image has a horizontal or vertical
  819.       //       black line in it.
  820.       var r = { startX: this.mStartX,         startY: this.mStartY,
  821.                 pageW:  this.mSelectionWidth, pageH:  this.mSelectionHeight };
  822.       r.startX = Math.round(r.startX / this.mFFZoomLevel)
  823.                             + this.mWindow.pageXOffset;
  824.       r.startY = Math.round(r.startY / this.mFFZoomLevel)
  825.                             + this.mWindow.pageYOffset;
  826.       r.pageW = Math.round(r.pageW / this.mFFZoomLevel);
  827.       r.pageH = Math.round(r.pageH / this.mFFZoomLevel);
  828.  
  829.       this.mCaptureFunc(this.mWindow, r, this.mCaptureArg, aGrabParam);
  830.     }
  831.   },
  832.  
  833.   deinitCanvasInPopup: function()
  834.   {
  835.     if (this.mPopupElem)
  836.     {
  837.       this.mPopupElem.removeEventListener("mousedown", this, true);
  838.       this.mPopupElem.removeEventListener("mousemove", this, true);
  839.       this.mPopupElem.removeEventListener("mouseup", this, true);
  840.       this.mPopupElem.removeEventListener("keypress", this, true);
  841.     }
  842.  
  843.     if (this.mPortionBoxElem)
  844.     {
  845.       this.mPortionBoxElem.removeEventListener("click", this, true);
  846.       this.mPortionBoxElem.removeEventListener("mousedown", this, true);
  847.     }
  848.  
  849.     if (this.mCanvasElem)
  850.     {
  851.       var canvascontext = this.mCanvasElem.getContext("2d");
  852.       if (canvascontext)
  853.       {
  854.         canvascontext.clearRect(0, 0,
  855.                                 this.mCanvasRect.pageW, this.mCanvasRect.pageH);
  856.       }
  857.     }
  858.     if (this.mWinCanvasElem)
  859.     {
  860.       var canvascontext = this.mWinCanvasElem.getContext("2d");
  861.       if (canvascontext)
  862.       {
  863.         canvascontext.clearRect(0, 0,
  864.                                 this.mCanvasRect.pageW, this.mCanvasRect.pageH);
  865.       }
  866.     }
  867.  
  868.     // If no capture was done, notify caller after canvas popup is done.
  869.     if (!this.mDidCapture && this.mCaptureFunc)
  870.     {
  871.       var self = this;
  872.       setTimeout(function() { self.mCaptureFunc(self.mWindow, null,
  873.                                                 self.mCaptureArg) }, 0);
  874.  
  875.     }
  876.   },
  877.  
  878.   cleanUp: function()
  879.   {
  880.     if (this.mPopupElem)
  881.       this.mPopupElem.hidePopup();
  882.  
  883.     this.mIsDrawing = false;
  884.     this.mDidCapture = false;
  885.   },
  886.  
  887. /*
  888.   pearlDumpObj: function(aLabel, aObj)
  889.   {
  890.     if (aObj)
  891.     {
  892.       if (aLabel)
  893.         dump(aLabel + ":\n");
  894.       for (var p in aObj)
  895.         dump("  " + p + ": " + aObj[p] + "\n");
  896.     }
  897.   },
  898. */
  899.  
  900. /*
  901.   dumpBoxes: function(aElem, aIndent)
  902.   {
  903.     if (!aIndent)
  904.       aIndent = "";
  905.  
  906.     if (aElem) try
  907.     {
  908.       var node = aElem.QueryInterface(Components.interfaces.nsIDOMNode);
  909.       var w;
  910.       var h;
  911.       var b = "unknown";
  912.       if (node.boxObject)
  913.       {
  914.         w = node.boxObject.width;
  915.         h = node.boxObject.height;
  916.         b = h + (node.boxObject.y - node.parentNode.boxObject.y);
  917.       }
  918.       else
  919.       {
  920.         w = aElem.clientWidth;
  921.         h = aElem.clientHeight;
  922.       }
  923.       var name = node.nodeName;
  924.       var id = node.id ? node.id : "-none-";
  925.       var childCount = node.childNodes.length;
  926.       dump(aIndent + name + " (" + id + ") - w: " + w + ", h: " + h
  927.            + ", b: " + b + " (" + childCount + " children)\n");
  928.       for (var child = node.firstChild; child != null; child = child.nextSibling)
  929.         this.dumpBoxes(child, "  " + aIndent);
  930.     } catch (e) { dump(e + "\n"); }
  931.   },
  932. */
  933.  
  934.   endOfObject: true
  935. }
  936.  
  937.